﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using XmlServer.OperatorManager;

namespace XmlServer.Listeners
{

    
    #region Delegates

    public delegate void ListenerStartedEventHandler(object sender, string IPAddress, int Port);
    public delegate void ListenerStoppedEventHandler(object sender, string IPAddress, int Port);
    public delegate void ListenerPausedEventHandler(object sender, string IPAddress, int Port);
    public delegate void ListenerResumedEventHandler(object sender, string IPAddress, int Port);
    public delegate void ListenerDebugMessageEventHandler(object sender, string IPAddress, int Port, string Message);
    public delegate void ListenerRequestStartedEventHandler(object sender, string ClientIPAddress, int ClientPort);
    public delegate void ListenerRequestServicingEventHandler(object sender, string ClientIPAddress, int ClientPort, string RequestData);
    public delegate void ListenerRequestCompletedEventHandler(object sender, string ClientIPAddress, int ClientPort, string ReplyData, int Duration);
    public delegate void ListenerExceptionEventHandler(object sender, string ClassName, string MethodName, Exception err);

    #endregion
    
    public enum ListenerType
    {
        PriorityListener,
        ProcessingListener
    }

    public class Listener
    {

        #region Local Variables

        private ListenerType listenerType;
        private ListenerConfiguration listenerConfig;   // the configuration values for the listener
        private ListenerStatus listenerStatus;          // the current status of the listener
        
        private OperatorInvokers operatorInvokers;
        private PoolServicer.ClientPool clientPool;
        private PoolServicer.PoolServicer poolServicer;
        private TcpListener tcpListener;        
        private Listener otherListener;

        private Thread acceptClientsThread;
        
        #endregion

        #region Client Events
        
        public event ListenerStartedEventHandler ListenerStarted;
        public event ListenerStoppedEventHandler ListenerStopped;
        public event ListenerPausedEventHandler ListenerPaused;
        public event ListenerResumedEventHandler ListenerResumed;
        public event ListenerDebugMessageEventHandler ListenerDebugMessage;
        public event ListenerRequestStartedEventHandler ListenerRequestStarted;
        public event ListenerRequestServicingEventHandler ListenerRequestServicing;
        public event ListenerRequestCompletedEventHandler ListenerRequestCompleted;
        public event ListenerExceptionEventHandler ListenerException;

        #endregion

        #region Constructors

        public Listener(ListenerType ListenerType)
        {
            this.listenerType = ListenerType;
            this.listenerStatus = new ListenerStatus();
            this.listenerConfig = new ListenerConfiguration();
        }

        public Listener(ListenerType ListenerType, string IPAddress, int Port, int NumThreads, string OperatorsFolder)
        {
            this.listenerType = ListenerType;

            this.listenerStatus = new ListenerStatus();

            this.listenerConfig = new ListenerConfiguration();
            this.listenerConfig.IPAddress = IPAddress;
            this.listenerConfig.Port = Port;
            this.listenerConfig.NumberOfThreads = NumThreads;
            this.listenerConfig.OperatorsFolder = OperatorsFolder;
        }

        public Listener(ListenerType ListenerType, ListenerConfiguration listenerConfig)
        {
            this.listenerType = ListenerType;
            this.listenerConfig = listenerConfig;
            this.listenerStatus = new ListenerStatus();
        }


        #endregion

        #region Public Properties


        /// <summary>
        /// The type of listener:  Processing or Priority
        /// </summary>
        public ListenerType ListenerType
        {
            get { return this.listenerType; }
        }

        /// <summary>
        /// Collection of operatprs objects from the operators folder
        /// </summary>
        public OperatorInvokers OperatorInvokers
        {
            get
            {
                return this.operatorInvokers;
            }
            set
            {
                this.operatorInvokers = value;
            }
        }


        /// <summary>
        /// The configuration settings for this listener
        /// </summary>
        public ListenerConfiguration Configuration
        {
            get { return this.listenerConfig; }
            set { this.listenerConfig = value; }
        }


        /// <summary>
        /// The status of this listener
        /// </summary>
        public ListenerStatus Status
        {
            get { return this.listenerStatus; }
            set { this.listenerStatus = value; }
        }


        /// <summary>
        /// Indicates is we are currently listening
        /// </summary>
        public bool Listening
        {
            get { return this.Status.Listening; }            
        }


        /// <summary>
        /// Indicates is the listener is suspended/paused
        /// </summary>
        public bool Paused
        {
            get { return this.Status.Paused; }
        }

        /// <summary>
        /// Allow multiple listeners to "See" each other.  Allows a "priority" listener to collect statuses on the related listener
        /// </summary>
        public Listener OtherListener
        {
            get
            {
                return otherListener;
            }
            set
            {
                otherListener = value;
            }
        }

        public PoolServicer.PoolServicer PoolServicer
        {
            get { return this.poolServicer; }
        }

        #endregion

        #region Public Methods

        public void StartListener()
        {
            InvokeDebugMessageDelegate("Listener.StartListener() => Starting");

            if (this.Status.Listening == true) return; // already listening

            try
            {                
                Monitor.Enter(this);

                #region Step 1: Load the operator/DLLs from the operators folder
                if (!String.IsNullOrEmpty(this.Configuration.OperatorsFolder))
                {
                    InvokeDebugMessageDelegate("Listener.StartListener() => Creating the Operators Collection");
                    try
                    {
                        this.OperatorInvokers = new OperatorManager.OperatorInvokers(this, this.Configuration.OperatorsFolder);
                    }
                    catch (Exception err)
                    {
                        var wrappedError = new Exception(String.Format("Error in Listener.StartListener(): Failed to load the operators collection from '{0}'", this.Configuration.OperatorsFolder), err);
                        InvokeExceptionDelegate("Listener", "StartListener", wrappedError);
                        throw wrappedError;
                    }
                }
                #endregion

                #region Step 2: Create the client pool

                InvokeDebugMessageDelegate("Listener.StartListener() => Create Client Pool");
                try
                {
                    this.clientPool = new PoolServicer.ClientPool(this.Configuration.ClientPoolCapacity);
                }
                catch (Exception err)
                {
                    var wrappedError = new Exception(String.Format("Error in Listener.StartListener(): Failed create the client pool, initial capacity = {0}", this.Configuration.ClientPoolCapacity), err);
                    InvokeExceptionDelegate("Listener", "StartListener", wrappedError);
                    throw wrappedError;
                }

                #endregion

                #region Step 3: Create the Pool Servicer

                InvokeDebugMessageDelegate("Listener.StartListener() => Create Pool Servicer");
                try
                {
                    this.poolServicer = new PoolServicer.PoolServicer(this.Configuration.NumberOfThreads, this.clientPool, this);
                }
                catch (Exception err)
                {
                    var wrappedError = new Exception(String.Format("Error in Listener.StartListener(): Failed create the pool servicer, NumberOfThreads = {0}", this.Configuration.NumberOfThreads), err);
                    InvokeExceptionDelegate("Listener", "StartListener", wrappedError);
                    throw wrappedError;
                }

                #endregion

                #region Step 4: Start the Pool Servicer

                try
                {
                    this.poolServicer.Start();
                }
                catch (Exception err)
                {
                    var wrappedError = new Exception("Error in Listener.StartListener(): Failed to start the Pool Servicer.", err);
                    InvokeExceptionDelegate("Listener", "StartListener", wrappedError);
                    throw wrappedError;
                }
                #endregion

                #region Step 5: Start the TCP Listener

                InvokeDebugMessageDelegate("Listener.StartListener() => Creating TCP Listener Endpoint");
                IPEndPoint endpoint = null;
                try
                {
                    endpoint = this.Configuration.GetEndpoint();
                }
                catch (Exception err)
                {
                    var wrappedError = new Exception(String.Format("Error in Listener.StartListener(): Exception while creating the TCP Listener EndPoint, ipAddress = {0}, Port = {1}", this.Configuration.IPAddress, this.Configuration.Port), err);
                    InvokeExceptionDelegate("Listener", "StartListener", wrappedError);
                    throw wrappedError;
                }

                if (endpoint == null)
                {
                    var wrappedError = new Exception(String.Format("Error in Listener.StartListener(): Failed to create the TCP Listener EndPoint, ipAddress = {0}, Port = {1}", this.Configuration.IPAddress, this.Configuration.Port));
                    InvokeExceptionDelegate("Listener", "StartListener", wrappedError);
                    throw wrappedError;
                }

                try
                {
                    this.tcpListener = new TcpListener(endpoint);
                }
                catch (Exception err)
                {
                    var wrappedError = new Exception(String.Format("Error in Listener.StartListener(): Exception while creating the TCP Listener, ipAddress = {0}, Port = {1}", this.Configuration.IPAddress, this.Configuration.Port), err);
                    InvokeExceptionDelegate("Listener", "StartListener", wrappedError);
                    throw wrappedError;
                }

                #endregion

                #region Step 6: Create the thread for the incoming client messages / Client Message Pump

                InvokeDebugMessageDelegate("Listener.StartListener() => Creating the thread to accept client requests / Client Message Pump");
                try
                {
                    this.acceptClientsThread = new Thread(new ThreadStart(AcceptClients));
                }
                catch (Exception err)
                {
                    var wrappedError = new Exception("Error in Listener.StartListener(): Exception while creating the thread to accept client requests / Client Message Pump", err);
                    InvokeExceptionDelegate("Listener", "StartListener", wrappedError);
                    throw wrappedError;
                }

                #endregion

                #region Step 7: Start Listener and Client Message Pump

                InvokeDebugMessageDelegate("Listener.StartListener() => Starting TCP Listener");
                try
                {
                    this.tcpListener.Start();
                    //this.tcpListener.Start(this.Configuration.NumberOfThreads * 10);
                }
                catch (Exception err)
                {
                    var wrappedError = new Exception("Error in Listener.StartListener(): Exception while starting the TCP Listener", err);
                    InvokeExceptionDelegate("Listener", "StartListener", wrappedError);
                    throw wrappedError;
                }

                InvokeDebugMessageDelegate("Listener.StartListener() => Starting thread to listen for client requests / Client Message Pump");
                try
                {
                    this.acceptClientsThread.Start();
                }
                catch (Exception err)
                {
                    var wrappedError = new Exception("Error in Listener.StartListener(): Exception while starting the thread to listen for client requests / Client Message Pump", err);
                    InvokeExceptionDelegate("Listener", "StartListener", wrappedError);
                    throw wrappedError;
                }


                #endregion
            }
            catch (Exception err)
            {
                var wrappedError = new Exception("CATASTROPHIC ERROR in Listener.StartListener(): Could not start TCP listener, client pool, pool servicer, or Client Message Pump", err);
                InvokeExceptionDelegate("Listener", "StartListener", wrappedError);
                throw wrappedError;
            }
            finally
            {
                Monitor.Exit(this);
            }

            InvokeDebugMessageDelegate("Listener.StartListener() => Finished");
        }

        public void PauseListener()
        {
            // Stop the TcpListener but keep the Client Message Pump running

            InvokeDebugMessageDelegate("Listener.PauseListener() => Starting");

            //if (this.Status.Listening == false) return;    // we are not listening
            if (this.Status.Paused == true) return;        // we are already paused 

            InvokeDebugMessageDelegate("Listener.PauseListener() => Stopping the listener");
            try
            {
                Monitor.Enter(this);
                this.tcpListener.Stop();
                this.Status.Paused = true;

                InvokePausedDelegate();
            }
            catch (Exception err)
            {
                var wrappedError = new Exception("CATASTROPHIC ERROR in Listener.PauseListener(): Could not pause TCP listener", err);
                InvokeExceptionDelegate("Listener", "PauseListener", wrappedError);
                throw wrappedError;
            }
            finally
            {
                Monitor.Exit(this);
            }

            InvokeDebugMessageDelegate("Listener.PauseListener() => Finished");
        }

        public void ResumeListener()
        {
            // Start the TcpListener; the Client Message Pump should already be running

            InvokeDebugMessageDelegate("Listener.ResumeListener() => Starting");

            //if (this.Status.Listening == true) return;     // we are already listening
            if (this.Status.Paused == false) return;       // we are not paused

            InvokeDebugMessageDelegate("Listener.ResumeListener() => Starting the listener");
            try
            {
                Monitor.Enter(this);
                this.tcpListener.Start();
                this.Status.Paused = false;

                InvokeResumeDelegate();
            }
            catch (Exception err)
            {
                var wrappedError = new Exception("CATASTROPHIC ERROR in Listener.ResumeListener(): Could not start TCP listener", err);
                InvokeExceptionDelegate("Listener", "ResumeListener", wrappedError);
                throw wrappedError;
            }
            finally
            {
                Monitor.Exit(this);
            }

            InvokeDebugMessageDelegate("Listener.ResumeListener() => Finished");

        }




        /// <summary>
        /// Stop the listener.  Any existing Client messages and retained, but not processed
        /// </summary>
        public void StopListener()
        {
            InvokeDebugMessageDelegate("Listener.StopListener() => Starting");

            if (this.Status.Listening == false) return; // we are not listening

            try
            {
                Monitor.Enter(this);

                #region Set the global variables to "Stop"

                InvokeDebugMessageDelegate("Listener.StopListener() => Setting control variables to 'Stop'");
                this.Status.Listening = false;
                this.Status.Paused = false;                
                StringBuilder sbErrors = new StringBuilder();

                #endregion

                #region Stop the TCP Listener

                InvokeDebugMessageDelegate("Listener.StopListener() => Stopping the TCP Listener");
                try
                {
                    tcpListener.Stop();
                    // Note: AcceptTcpClient() is blocking.  Stopping the listener is supposed tp release the block (with an exception)                 
                }
                catch (Exception err)
                {
                    sbErrors.AppendFormat("Error in Listener.StopListener(): Failed to stop the TCP Listener, error = {0}\n", err.ToString());
                }

                #endregion

                #region Stop the Pool Servicer

                InvokeDebugMessageDelegate("Listener.StopListener() => Stopping the pool servicer");
                try
                {
                    poolServicer.Stop();
                }
                catch (Exception err)
                {
                    sbErrors.AppendFormat("Error in Listener.StopListener(): Failed to stop the Pool Servicer, error = {0}\n", err.ToString());
                }

                #endregion

                #region Stop the Client Message Pump

                InvokeDebugMessageDelegate("Listener.StopListener() => Stopping the CLient Message Pump to prevent new client requests");
                try
                {
                    if (this.acceptClientsThread != null)
                    {
                        Thread.Sleep(1000); // wait for the client message pump to stop
                        if (this.acceptClientsThread.IsAlive)
                        {
                            this.acceptClientsThread.Abort();
                        }
                    }

                }
                catch (Exception err)
                {
                    sbErrors.AppendFormat("Error in Listener.StopListener(): Failed to stop Client Message Pump, error = {0}\n", err.ToString());
                }

                #endregion


                if (sbErrors.Length > 0)
                {
                    // we got errors at some point while trying to stop the listener
                    throw new Exception(sbErrors.ToString());
                }

                this.Status.Paused = false;

                this.InvokeStopDelegate();
            
            }
            catch (Exception err)
            {
                var wrappedError = new Exception("CATASTROPHIC ERROR in Listener.StopListener(): Could not stop the listener and/or dependent objects", err);
                InvokeExceptionDelegate("Listener", "StopListener", wrappedError);
                throw wrappedError;
            }
            finally
            {
                Monitor.Exit(this);
            }

        }


        /// <summary>
        /// Stop the listener and clear out any Client requests that may be sitting in the pool
        /// </summary>
        public void ShutDown()
        {
            InvokeDebugMessageDelegate("Listener.ShutDown() => Starting");

            try
            {                
                StopListener();
            }
            catch (Exception err)
            {
                var wrappedError = new Exception("CATASTROPHIC ERROR in Listener.ShutDown(): Failed to stop listener", err);
                InvokeExceptionDelegate("Listener", "ShutDown", wrappedError);
                throw wrappedError;
            }
                
            // force client pool to empty
            try
            {
                Monitor.Enter(this);
                this.clientPool.Clear();
            }
            catch (Exception err)
            {
                var wrappedError = new Exception("CATASTROPHIC ERROR in Listener.ShutDown(): Failed to clear client pool", err);
                InvokeExceptionDelegate("Listener", "ShutDown", wrappedError);
                throw wrappedError;
            }
            finally
            {
                Monitor.Exit(this);
            }

            InvokeDebugMessageDelegate("Listener.ShutDown() => Finished");
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Start the listener
        /// </summary>
        private void AcceptClients()
        {
            this.Status.Listening = true;
            this.Status.Paused = false;

            this.InvokeStartDelegate();

            while (Status.Listening)
            {                

                if (!this.Status.Paused)
                {
                    // wait for a client
                    TcpClient client = null;
                    try
                    {
                        InvokeDebugMessageDelegate("Listener.AcceptClients(): Waiting for Client Requests");
                        client = tcpListener.AcceptTcpClient();
                    }         
                    catch (SocketException s)
                    {
                        // see if the listener stopped
                        if (!this.Status.Listening) return;                        
                    }
                    catch (Exception err)
                    {
                        client = null;

                        // invoke the delegate to report the error, but don't throw an exception
                        var wrappedError = new Exception("CATASTROPHIC ERROR in Listener.AcceptClients(): Exception while trying to AcceptTcpClient().  Listener will continue to wait for client requests.", err);
                        InvokeExceptionDelegate("Listener", "AcceptClients", wrappedError);
                    }

                    if (client != null)
                    {
                        try
                        {
                            InvokeDebugMessageDelegate("Listener.AcceptClients(): Received client request; Adding to queue.");

                            // create the client handler and add to the pool
                            var clientHandler = new PoolServicer.ClientHandler(client, this);
                            InvokeClientRequestStartedDelegate(clientHandler.ClientIPAddress, clientHandler.ClientPort);

                            clientPool.Add(clientHandler);

                        }
                        catch (Exception err)
                        {
                            var wrappedError = new Exception("CATASTROPHIC ERROR in Listener.AcceptClients(): Recieved client request and failed to create client handler and add to pool", err);
                            InvokeExceptionDelegate("Listener", "AcceptClients", wrappedError);
                            throw wrappedError;

                        }
                    }
                    
                }

                // sleep the Client Message Pump
                Thread.Sleep(50);


            }
            
        }

        #endregion

        #region Delegate Methods

        internal void InvokeStartDelegate()
        {
            try
            {
                Monitor.Enter(this);
                if (ListenerStarted != null)
                {
                    ListenerStarted(this, this.Configuration.IPAddress, this.Configuration.Port);
                }
            }
            catch { }
            finally
            {
                Monitor.Exit(this);
            }
        }

        internal void InvokeStopDelegate()
        {
            try
            {
                Monitor.Enter(this);
                if (ListenerStopped != null)
                {
                    ListenerStopped(this, this.Configuration.IPAddress, this.Configuration.Port);
                }
            }
            catch { }
            finally
            {
                Monitor.Exit(this);
            }
        }

        internal void InvokePausedDelegate()
        {
            try
            {
                Monitor.Enter(this);
                if (ListenerPaused != null)
                {
                    ListenerPaused(this, this.Configuration.IPAddress, this.Configuration.Port);
                }
            }
            catch { }
            finally
            {
                Monitor.Exit(this);
            }
        }

        internal void InvokeResumeDelegate()
        {
            try
            {
                Monitor.Enter(this);
                if (ListenerResumed != null)
                {
                    ListenerResumed(this, this.Configuration.IPAddress, this.Configuration.Port);
                }

            }
            catch { }
            finally
            {
                Monitor.Exit(this);
            }
        }

        internal void InvokeDebugMessageDelegate(string Message)
        {
            try
            {
                Monitor.Enter(this);
                if (ListenerDebugMessage != null)
                {
                    ListenerDebugMessage(this, this.Configuration.IPAddress, this.Configuration.Port, Message);
                }

            }
            catch { }
            finally
            {
                Monitor.Exit(this);
            }
        }

        internal void InvokeClientRequestCompletedDelegate(string ClientIPAddress, int ClientPort, string ReplyData, int Duration)
        {
            try
            {
                Monitor.Enter(this);
                if (ListenerRequestCompleted != null)
                {
                    ListenerRequestCompleted(this, ClientIPAddress, ClientPort, ReplyData, Duration);
                }

            }
            catch { }
            finally
            {
                Monitor.Exit(this);
            }
        }

        internal void InvokeClientRequestServicingDelegate(string ClientIPAddress, int ClientPort, string RequestData)
        {
            try
            {
                Monitor.Enter(this);
                if (ListenerRequestServicing != null)
                {
                    ListenerRequestServicing(this, ClientIPAddress, ClientPort, RequestData);
                }

            }
            catch { }
            finally
            {
                Monitor.Exit(this);
            }
        }

        internal void InvokeClientRequestStartedDelegate(string ClientIPAddress, int ClientPort)
        {
            try
            {
                Monitor.Enter(this);
                if (ListenerRequestStarted != null)
                {
                    ListenerRequestStarted(this, this.Configuration.IPAddress, this.Configuration.Port);
                }

            }
            catch { }
            finally
            {
                Monitor.Exit(this);
            }
        }

        internal void InvokeExceptionDelegate(string ClassName, string MethodName, Exception err)
        {
            try
            {
                Monitor.Enter(this);
                if (ListenerException != null)
                {
                    ListenerException(this, ClassName, MethodName, err);
                }

            }
            catch { }
            finally
            {
                Monitor.Exit(this);
            }
        }

        #endregion
    }
}
